# 第12章 Proxy

Proxy 用于修改某些操作的默认行为，等同于在语言层面做出修改，所以属于一种“元编程”（meta programming），即对编程语言进行编程。

```
let target = {}
let proxy = new Proxy(target, {
    get: function(t, p) {
        console.log(t === target)
        return 35;
    } 
})
```

技巧：

```
var object1 = { proxy: new Proxy(target, {
    get: function(t, p) {
        console.log(t === target)
        return 35;
    } 
}) };
object1.a;
```

拦截操作： get set has deleteProperty getOwnPropertyDescriptor defineProperty preventExtensions getProtoTypeOf isExtensible setProtoTypeOf apple construct


## get
```
function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-5] // undefined

```

## set
## apply
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数，分别是目标对象、目标对象的上下文对象（this）和目标对象的参数数组。

## has

has方法用来拦截HasProperty操作，即判断对象是否具有某个属性时，这个方法会生效。典型的操作就是in运算符。

has方法可以接受两个参数，分别是目标对象、需查询的属性名。

值得注意的是，has方法拦截的是HasProperty操作，而不是HasOwnProperty操作，即has方法不判断一个属性是对象自身的属性，还是继承的属性。

## ownKeys

```
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in循环

```

## this

在 Proxy 代理的情况下，目标对象内部的this关键字会指向 Proxy 代理。

# 第13章 Reflect

1.  从Reflect对象上可以拿到语言内部的方法。

2.  修改某些Object方法的返回结果，让其变得更合理。
3.  让Object操作都变成函数行为。

```
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

```

4. Reflect对象的方法与Proxy对象的方法一一对应，只要是Proxy对象的方法，就能在Reflect对象上找到对应的方法。


# 第14章 Promise
就是一个容器，里面保存着某个未来才会结束的事件（通常是一个异步操作）的结果。从语法上说，Promise 是一个对象，从它可以获取异步操作的消息。Promise 提供统一的 API，各种异步操作都可以用同样的方法进行处理。
两个特点
（1）对象的状态不受外界影响
（2）一旦状态改变，就不会再变，任何时候都可以得到这个结果。
    从pending变为fulfilled
    从pending变为rejected
    

##### 基本用法
```js
    const promise = new Promise(function(resolve, reject) {
        console.log("promise1");
        setTimeout(function() {
            let value = Math.random();
            console.log("promise", value);
          if (value < 0.5) {
            resolve(value);
          } else {
            reject();
          }
        }, 2000);
    });
    console.log("promise3");
    setTimeout(()=>{
        console.log("promise2");
        promise.then(function(v){
            console.log("promise5", v);
        }, (v)=>{
            console.log("promise6", v);
        })
    }, 1000);

```

##### then resolved rejected
```
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  reject();
});

promise.then(function() {
  console.log('resolved.');
}, function() {
  console.log('reject.');
});

```

##### Promise 之间调用
```
const p1 = new Promise(function (resolve, reject) {
  console.log("Promise p11");
  setTimeout(() => {
    console.log("Promise p12");
    reject(new Error("fail"));
  }, 3000);
});

const p2 = new Promise(function (resolve, reject) {
  console.log("Promise p21");
  setTimeout(() => {
    console.log("Promise p22");
    resolve(p1);
  }, 1000);
});

p2.then((result) => {
  console.log(result);
}).catch((error) => {
  console.log(error);
});

```

##### 调用resolve或reject并不会终结 Promise 的参数函数的执行。
```
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
```

##### Promise.prototype.then() 

##### Promise.prototype.catch() 
Promise 在resolve语句后面，再抛出错误，不会被捕获，等于没有抛出。因为 Promise 的状态一旦改变，就永久保持该状态，不会再变了。

```
new Promise((resolve, reject) => {
  reject(1);
}).then(r => {
  console.log(r, "resolve");
}).catch(r => {
  console.log(r, "reject");
})
```

Promise 对象后面要跟catch()方法，这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象，因此后面还可以接着调用then()方法。


##### Promise.prototype.finally()
finally本质上是then方法的特例。

```
new Promise((resolve, reject) => {
  reject(1);
}).then(r => {
  console.log(r, "resolve");
}).catch(r => {
  console.log(r, "reject");
}).finally(() => {
  console.log(r, "finally")
});
```

##### Promise.all 
Promise.all()方法的参数可以不是数组，但必须具有 Iterator 接口，且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定，分成两种情况。

```
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("hello111");
    resolve("hello");
  }, 1000);
})
  .then((result) => result)
  .catch((e) => e);

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("报错了111");
    throw new Error("报错了");
  }, 2000);
})
  .then((result) => result)
  .catch((e) => e);

Promise.all([p1, p2])
  .then((result) => {
    console.log("result1");
    console.log(result);
  })
  .catch((e) => {
    console.log("result2");
    console.log(e, "123");
  }).finally((e) => {
    console.log("finally");
    console.log(e);
  });
```
##### Promise.race 
只要p1、p2、p3之中有一个实例率先改变状态，p的状态就跟着改变。那个率先改变的 Promise 实例的返回值，就传递给p的回调函数。


#### Promise.allSettled()
方法接受一组 Promise 实例作为参数，包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果，不管是fulfilled还是rejected，包装实例才会结束。该方法由 ES2020 引入。

#### Promise.any()
Promise.any()跟Promise.race()方法很像，只有一点不同，就是不会因为某个 Promise 变成rejected状态而结束。

##### Promise.resolve()
有时需要将现有对象转为 Promise 对象，Promise.resolve()方法就起到这个作用
（1）参数是一个 Promise 实例
（2）参数是一个thenable对象

```
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
```

（3）参数不是具有then方法的对象，或根本就不是对象。
立即执行没有什么意义

（4）不带有任何参数
需要注意的是，立即resolve()的 Promise 对象，是在本轮“事件循环”（event loop）的结束时执行，而不是在下一轮“事件循环”的开始时。

```
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

```

##### Promise.reject(reason)
Promise.reject()方法的参数，会原封不动地作为reject的理由，变成后续方法的参数。这一点与Promise.resolve方法不一致。

```
const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
```


##### Promise.try
让同步函数同步执行，异步函数异步执行
第一种写法是用async函数来写
```
const f = () => console.log('now');
(async () => f())();
console.log('next');
```
第二种写法是使用new Promise()。

```
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
```

try ????? 
```
const f = () => console.log('now');
Promise.try(f);
console.log('next');
```




# 第15章 Iterater 和 for...of 循环

只是可遍历：

```js
const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};
```

直接调用

```js
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

```


对于原生部署 Iterator 接口的数据结构，不用自己写遍历器生成函数，for...of循环会自动遍历它们。除此之外，其他数据结构（主要是对象）的 Iterator 接口，都需要自己在Symbol.iterator属性上面部署，这样才会被for...of循环遍历。

一个对象如果要具备可被for...of循环调用的 Iterator 接口，就必须在Symbol.iterator的属性上部署遍历器生成方法（原型链上的对象具有该方法也可）。

例子：
```js
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 10)) {
  console.log(value); // 0, 1, 2
}
```


```js
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
```

类似数组 ？？??
```js
let iterable = {
  0: 'a',
  b: 'b',
  3: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}
```


调用 Iterator 接口的场合
（1）解构赋值
```js
    let arr = [...iterable];
    
```
(2) ...

(3) yield

(4)     其他



```js
const arr = ["red", "green", "blue"];
const arr2 = ["red1", "green1", "blue1"];

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for (let v of obj) {
  console.log(v); // red green blue
}

obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr2);
for (let v of obj) {
  console.log(v); // red green blue
}
```


for...in循环读取键名，for...of循环读取键值。如果要通过for...of循环，获取数组的索引，

```js
var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}
```


for in object 

for of 下标数值

for...of循环调用遍历器接口，数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。





Set 和 Map 结构
Set 和 Map 结构也原生具有 Iterator 接口，可以直接使用for...of循环。

返回是数组 [key, value]


```js
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
```


无法中途跳出forEach循环，break命令或return命令都不能奏效。

for...in循环有几个缺点。

数组的键名是数字，但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
for...in循环不仅遍历数字键名，还会遍历手动添加的其他键，甚至包括原型链上的键。
某些情况下，for...in循环会以任意顺序遍历键名。

# Generator 函数的语法

#### 基本概念：Generator 函数是一个状态机，封装了多个内部状态。

```js
function* helloWorldGenerator() {
    console.log(1);
  yield 'hello';
      console.log(2);
  yield 'world';
      console.log(3);
  return 'ending';
}
    console.log(4);
var hw = helloWorldGenerator();
    console.log(5);
```

推荐
```js
function* foo(x, y) { ··· }
```

#### yield 表达式

#### yield用法
yield表达式只能用在 Generator 函数里面，用在其他地方都会报错。

yield表达式如果用在另一个表达式之中，必须放在圆括号里面。
yield表达式用作函数参数或放在赋值表达式的右边，可以不加括号。

#### 与 Iterator 接口的关系

#### next 方法参数

```js
function* foo(x) {
  console.log("s 1", x, y, z);
  var y = 2 * (yield (x + 1));

  console.log("s 2", x, y, z);
  var z = yield (y / 3);
  console.log("s 3", x, y, z);
  return (x + y + z);
}
var a = foo(5);
a.next();
a.next();
a.next();

var a = foo(5);
a.next(1);
a.next(2);
a.next(3);

```

#### 3 for...of循环

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    console.log("---1", prev, curr)
    yield curr;
    [prev, curr] = [curr, prev + curr];
    console.log("---2", prev, curr)
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}


#### return 前后

```js
function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
```

```js
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2
```

##### throw 

```js
var g = function* () {
  try {
    yield 1;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
    i.next('a');
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}

```


```js 
var g = function* () {
  try {
    console.log("1");
    yield 100;
    console.log("2");
  } catch (e) {
    console.log("内部捕获", e);
  }
};

var i = g();
console.log("3");
console.log(i.next());
console.log("4");

try {
  console.log(5);
  i.next();
  console.log(6);
  i.throw("a");
  console.log(7);
  i.throw("b");
  console.log(8);
} catch (e) {
  console.log("外部捕获", e);
}
```



##### 不要混淆遍历器对象的throw方法和全局的throw命令。


##### throw方法被捕获以后，会附带执行下一条yield表达式


#### return 可以返回给定的值，并且终结遍历 Generator 函数。

注意 finally 


#### yield* 表达式
    
    嵌套使用 yield
    
    
#### Generator  this
Generator 函数总是返回一个遍历器，ES6 规定这个遍历器是 Generator 函数的实例，也继承了 Generator 函数的prototype对象上的方法。



# Generator 函数的异步应用

基本概念：一个任务不是连续完成的

### 异步方式

##### 1. 函数回调

```js
setTimeout(()=>{
  console.log("Hello World");
}, 1000)

```

```js
console.log(1)
console.time("123")
setTimeout(()=>{
  console.log(3);
  console.timeEnd("123")
}, 1000)

console.time("456")
for(let i = 0; i<100000000000;i++){
  let a = Math.sin(i);
}
console.log(2)
console.timeEnd("456")
```

##### 2. Promise

解决 "回调函数地狱"

Promise 的最大问题是代码冗余，原来的任务被 Promise 包装了一下，不管什么操作，一眼看去都是一堆then，原来的语义变得很不清楚。


##### 3. Generator 

"协程"（coroutine），意思是多个线程互相协作，完成异步任务


#####  Thunk 函数

一种意见是"传值调用"（call by value），即在进入函数体之前，就计算x + 5的值（等于 6），再将这个值传入函数f。C 语言就采用这种策略。

另一种意见是“传名调用”（call by name），即直接将表达式x + 5传入函数体，只在用到它的时候求值。Haskell 语言采用这种策略。

JavaScript 语言是传值调用，它的 Thunk 函数含义有所不同。在 JavaScript 语言中，Thunk 函数替换的不是表达式，而是多参数函数，将其替换成一个只接受回调函数作为参数的单参数函数。


# async 函数


#### async
async 函数是什么？一句话，它就是 Generator 函数的语法糖。
一比较就会发现，async函数就是将 Generator 函数的星号（*）替换成async，将yield替换成await，仅此而已。



```js
console.log("1", new Date().getTime());
function timeout(ms) {
  return new Promise((resolve) => {
    console.log("3", new Date().getTime());
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  console.log("2", new Date().getTime());
  await timeout(ms);
  console.log(value);
  return "6";
}

console.log("4", new Date().getTime());
asyncPrint("hello world", 5000).then((data) => {
  console.log("7", new Date().getTime(), data);
});
```


async函数内部抛出错误，会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

#### await 命令

* 正常情况下，await命令后面是一个 Promise 对象，返回该对象的结果。如果不是 Promise 对象，就直接返回对应的值。

* await命令后面是一个thenable对象（即定义了then方法的对象），那么await会将其等同于 Promise 对象。

```js
class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
```

#### 错误处理 

#### 使用注意点
* 第一点，前面已经说过，await命令后面的Promise对象，运行结果可能是rejected，所以最好把await命令放在try...catch代码块中。
* 第二点，多个await命令后面的异步操作，如果不存在继发关系，最好让它们同时触发。Promise.all

```js
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
```

例子1
```js 
function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  })
}

let getFoo = async () => {
  console.log(2, "getFoo");
  await sleep(1000);
  return "foo";
};

let getBar = async () => {
  console.log(1, "getBar");
  await sleep(1500);
  return "bar";
};

let fooPromise = getFoo(1000);
let barPromise = getBar(2000);
console.log(fooPromise);
console.log(fooPromise);
let foo = await fooPromise;
let bar = await barPromise;

console.log(foo);
console.log(bar);

```

例子2


```js

async function getFoo(interval) {
  return new Promise((resolve) => {
    setTimeout(resolve, interval);
    
  });
}

async function getBar(interval) {
  return new Promise((resolve) => {
    setTimeout(resolve, interval);
    
  });
}


let fooPromise = getFoo(1000);
let barPromise = getBar(2000);
console.log(fooPromise);
console.log(barPromise);
let foo = await fooPromise;
let bar = await barPromise;

console.log(foo); // 这个要出结果怎么返回 ？？？？
console.log(bar); // 这个要出结果怎么返回 ？？？？
```

* 第三点，await命令只能用在async函数之中，如果用在普通函数，就会报错。


#### async 函数的实现原理

就是将 Generator 函数和自动执行器，包装在一个函数里。


#### 与其他异步处理方法的比较


```js
let p = Promise.resolve();
  // 使用then方法，添加所有动画
for(let anim of [1,2,3,4,5]) {
  p = p.then(function(val) {
    console.log(val, anim);
    return anim;
  });
}
```


```js
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve("resolve");
  }, 1000);
});

const p2 = p1.then((v) => {
  console.log(v, "p1");
  return "p1";
});

p2.then((v) => {
  console.log(v, "p2");
  return "p2";
});
```




## Class 的基本语法

#### Es5 语法类

```js
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);
```

#### 属性定义
```js
let methodName = 'getArea';
class Square {
  constructor(length) {
// ...
}
  ["pp" + "ii"]() {
// ...
} }
```

#### 类和模块的内部，默认就是严格模式

#### 不存在变量提升

```js

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

{
 class Bar extends Foo1 {
  }
  let Foo1 = class {};
 
}
```

#### 不提供私有方法 命名替代

#### 索性将私有方法移出模块，因为模块内部的所有方法都 是对外可见的。

#### 取值函数（getter）和存值函数（setter）
与 ES5 一样，在“类”的内部可以使用get和set关键字，对某个属性设置存值函数和取值函数，拦截该属性的存取行为。



# 第15章 Class的基础

##### 在子类的构造函数中，只有调用super之后，才可以使用this关键字，否则会报错。

##### 父类的静态方法，也会被子类继承


#### super
> 第一种情况，super作为函数调用时，代表父类的构造函数。ES6 要求，子类的构造函数必须执行一次super函数。
new.target指向当前正在执行的函数

> 第二种情况，super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类。

> 由于super指向父类的原型对象，所以定义在父类实例上的方法或属性，是无法通过super调用的。

```js
class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m 

```

> super.print() 其实是执行的 super.print.call(this)

> 由于this指向子类实例，所以如果通过super对某个属性赋值，这时super就是this，赋值的属性会变成子类实例的属性。

> 如果super作为对象，用在静态方法之中，这时super将指向父类，而不是父类的原型对象。

```js
class Parent {
  static myMethod(msg) {
    console.log("Parent myMethod static", msg);
  }

  myMethod(msg) {
    console.log("Parent instance", msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    console.log("Child static myMethod", msg);
    super.myMethod(msg);
  }


  myMethod(msg) {
    console.log("Child myMethod", msg);
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); //
```

> 在子类的静态方法中通过super调用父类的方法时，方法内部的this指向当前的子类，而不是子类的实例


```js
class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B); // true
    console.log(super.valueOf() ); // true
  }
}

let b = new B();
```

####  prototype 和__proto__
>（1）子类的__proto__属性，表示构造函数的继承，总是指向父类。

> （2）子类prototype属性的__proto__属性，表示方法的继承，总是指向父类的prototype属性。


```js
class A {
}
class B {
}
B.prototype.__proto__ = A.prototype
B.__proto__ = A
```

```js
class A {}

class B extends A {
  constructor() {
    super();
  }
}
let a = new A();
let b = new B();
console.log(b.__proto__ === A)
console.log(b.__proto__.constructor === B)
console.log(B.__proto__ === A)
b.__proto__.__proto__ === a.__proto__

```

```js
class A extends Object {
}

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
```

```js
class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
```


#### 原生构造函数的继承 
>  Array.apply 原生构造函数是无法继承的，是因为子类无法获得原生构造函数的内部属性，通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this，也就是说，原生构造函数的this无法绑定，导致拿不到内部属性
>  
>  ES6 规定Object构造函数会忽略参数。

#### Mixin 模式的实现 忽略


#### Module 的语法

##### ES6 模块的设计思想，是尽量的静态化，使得编译时就能确定模块的依 赖关系，以及输入和输出的变量。 

##### 由于 ES6 模块是编译时加载，使得静态分析成为可能。

##### export 命令除了输出变量，还可以输出函数或类

##### import 后面的 from 指定模块文件的位置，可以是相对路径，也可以 是绝对路径， .js 后缀可以省略。如果只是模块名，不带有路径，那么 必须有配置文件，告诉 JavaScript 引擎该模块的位置。


##### 注意， import 命令具有提升效果，会提升到整个模块的头部，首先执行。

##### 由于 import 是静态执行，所以不能使用表达式和变量，这些只有在运 行时才能得到结果的语法结构。


##### export default 命令用于指定模块的默认输出。显然，一个模块只能有一个默认输出，因此 export default 命令只能使用一次。所以，import 命令后面才不用加大括号，因为只可能对应一个方法。

##### import _, { each, each as forEach } from 'lodash';

##### export 与 import 的复合写法
```js
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module'; export { foo, bar };

export { default } from 'foo';
```

##### 模块的继承

##### import()
(1)按需加载
(2)条件加载
(3)动态的模块路径

## 模块的加载

### 传统用法

默认情况下，浏览器是同步加载 JavaScript 脚本，即渲染引擎遇到<script>标签就会停下来，等到执行完脚本，再继续向下渲染。如果是外部脚本，还必须加入脚本下载的时间。

defer与async的区别是：defer要等到整个页面在内存中正常渲染结束（DOM 结构完全生成，以及其他脚本执行完成），才会执行；async一旦下载完，渲染引擎就会中断渲染，执行这个脚本以后，再继续渲染。一句话，defer是“渲染完再执行”，async是“下载完就执行”。另外，如果有多个defer脚本，会按照它们在页面出现的顺序加载，而多个async脚本是不能保证加载顺序的。


```html
<!DOCTYPE html>

<html>
  <body>
    <script type="module" src="./A.js"></script>
    <script type="module" src="./B.js"></script>

    <script type="module">
      import * as A from "./A.js";
      import * as B from "./A.js";
      console.log(A);
      console.log(B);
      
    </script>

  </body>
</html>
```

对于外部的模块脚本（上例是foo.js），有几点需要注意。

代码是在模块作用域之中运行，而不是在全局作用域运行。模块内部的顶层变量，外部不可见。 
 全局变量不可见

模块脚本自动采用严格模式，不管有没有声明use strict。

模块之中，可以使用import命令加载其他模块（.js后缀不可省略，需要提供绝对 URL 或相对 URL），也可以使用export命令输出对外接口。
模块之中，顶层的this关键字返回undefined，而不是指向window。也就是说，在模块顶层使用this关键字，是无意义的。
同一个模块如果加载多次，将只执行一次。


### ES6模块与 CommonJS 模块的差异

CommonJS 模块输出的是一个值的拷贝，ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载，ES6 模块是编译时输出接口。
CommonJS 模块的require()是同步加载模块，ES6 模块的import命令是异步加载，有一个独立的模块依赖的解析阶段。

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候，遇到模块加载命令import，就会生成一个只读引用。等到脚本真正执行时，再根据这个只读引用，到被加载的那个模块里面去取值。换句话说，ES6 的import有点像 Unix 系统的“符号连接”，原始值变了，import加载的值也会跟着变。因此，ES6 模块是动态引用，并且不会缓存值，模块里面的变量绑定其所在的模块。


babel-node m2.js 报错？？？

### Node.js 的模块加载方法

CommonJS 模块是 Node.js 专用的，与 ES6 模块不兼容。语法上面，两者最明显的差异是，CommonJS 模块使用require()和module.exports，ES6 模块使用import和export。


.mjs文件总是以 ES6 模块加载，.cjs文件总是以 CommonJS 模块加载，.js文件的加载取决于package.json里面type字段的设置。

ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件，会报错，只有import命令才可以加载.mjs文件。反过来，.mjs文件里面也不能使用require命令，必须使用import。

#### package.json 的 main 字段
（1）子目录别名
 (2) exports字段的别名如果是.，就代表模块的主入口，优先级高于main字段，并且可以直接简写成exports字段的值。
#### package.json 的 exports 字段
（3）条件加载 
可以为 ES6 模块和 CommonJS 指定不同的入口

#### CommonJS 模块加载 ES6 模块

#### ES6 模块加载 CommonJS 模块


#### 循环加载
#### CommonJS 模块的加载原理
CommonJS 的一个模块，就是一个脚本文件。require命令第一次加载该脚本，就会执行整个脚本，然后在内存生成一个对象。
```js
{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}
```
上面代码就是 Node 内部加载模块后生成的一个对象。该对象的id属性是模块名，exports属性是模块输出的各个接口，loaded属性是一个布尔值，表示该模块的脚本是否执行完毕。其他还有很多属性，这里都省略了。

ES6 处理“循环加载”与 CommonJS 有本质的不同

#### ES6 模块的循环加载
ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用，如果使用import从一个模块加载变量（即import foo from 'foo'），那些变量不会被缓存，而是成为一个指向被加载模块的引用，需要开发者自己保证，真正取值的时候能够取到值。




#### 块级作用域
（1）let 取代 var

（2）全局常量和线程安全
    let 只是单线程
    
#### 字符串
静态字符串一律使用单引号或反引号，不使用双引号。动态字符串使用反引号。


#### 解构赋值
使用数组成员对变量赋值时，优先使用解构赋值。
函数的参数如果是对象的成员，优先使用解构赋值。
如果函数返回多个值，优先使用对象的解构赋值，而不是数组的解构赋值。这样便于以后添加返回值，以及更改返回值的顺序。

#### 对象
单行定义的对象，最后一个成员不以逗号结尾。多行定义的对象，最后一个成员以逗号结尾。

如果添加属性不可避免，要使用Object.assign方法。

另外，对象的属性和方法，尽量采用简洁表达法，这样易于描述和书写。


#### 函数
立即执行函数可以写成箭头函数的形式。
简单的、单行的、不会复用的函数，建议采用箭头函数。如果函数体较为复杂，行数较多，还是应该采用传统的函数写法。

不要在函数体内使用 arguments 变量，使用 rest 运算符（...）代替。因为 rest 运算符显式表明你想要获取参数，而且 arguments 是一个类似数组的对象，而 rest 运算符可以提供一个真正的数组。



#### Class
总是用 Class，取代需要 prototype 的操作。因为 Class 的写法更简洁，更易于理解。

使用extends实现继承，因为这样更简单，不会有破坏instanceof运算的危险。


#### 模块
首先，Module 语法是 JavaScript 模块的标准写法，坚持使用这种写法。使用import取代require。
使用export取代module.exports。

不要在模块输入中使用通配符。因为这样可以确保你的模块之中，有一个默认输出（export default）。
如果模块默认输出一个函数，函数名的首字母应该小写。
如果模块默认输出一个对象，对象名的首字母应该大写。


### 读懂规格

#### 抽象操作
（abstract operations）就是引擎的一些内部方法，外部不能调用。规格定义了一系列的抽象操作，规定了它们的行为，留给各种引擎自己去实现。

所有的 JavaScript 函数都有一个内部属性[[Call]]，用来运行该函数。
F是一个函数对象，[[Call]]是它的内部方法，F.[[call]]()表示运行该函数，V表示[[Call]]运行时this的值，argumentsList则是调用时传入函数的参数。


#### [[Notation]] 
ES6 规格不会写obj.Prototype，而是写obj.[[Prototype]]。一般来说，使用[[Notation]]这种书写法的属性，都是对象的内部属性。


#### Completion Record
每一个语句都会返回一个 Completion Record


#### 抽象操作的标准流程

#### 相等运算符
如果x不是正常值（比如抛出一个错误），中断执行。
如果y不是正常值，中断执行。
如果Type(x)与Type(y)相同，执行严格相等运算x === y。
如果x是null，y是undefined，返回true。
如果x是undefined，y是null，返回true。
如果Type(x)是数值，Type(y)是字符串，返回x == ToNumber(y)的结果。
如果Type(x)是字符串，Type(y)是数值，返回ToNumber(x) == y的结果。
如果Type(x)是布尔值，返回ToNumber(x) == y的结果。
如果Type(y)是布尔值，返回x == ToNumber(y)的结果。
如果Type(x)是字符串或数值或Symbol值，Type(y)是对象，返回x == ToPrimitive(y)的结果。
如果Type(x)是对象，Type(y)是字符串或数值或Symbol值，返回ToPrimitive(x) == y的结果。
返回false。

#### 数组的空位

const a1 = [undefined, undefined, undefined];
const a2 = [, , ,];

a1.length // 3
a2.length // 3

a1[0] // undefined
a2[0] // undefined

a1[0] === a2[0] // true

0 in a1 // true
0 in a2 // false

a1.hasOwnProperty(0) // true
a2.hasOwnProperty(0) // false

Object.keys(a1) // ["0", "1", "2"]
Object.keys(a2) // []

a1.map(n => 1) // [1, 1, 1]
a2.map(n => 1) // [, , ,]


#### 数组的 map 方法

const arr = [, , ,];
arr.map(n => {
  console.log(n);
  return 1;
}) // [, , 

arr是一个全是空位的数组，map方法遍历成员时，发现是空位，就直接跳过，不会进入回调函数。



#### 同步遍历器的问题
   对于 异步操作不友好，
   Iterator 协议里面next()方法只能包含同步操作。

```js

function idMaker() {
  let index = 0;

  return {
    next: function() {
      return {
        value: new Promise(resolve => 
            setTimeout(() => resolve(index++), 1000)),
        done: false
      };
    }
  };
}

const it = idMaker();

it.next().value.then(o => console.log(o)) // 1
it.next().value.then(o => console.log(o)) // 2
it.next().value.then(o => console.log(o)) 
 ```
 
 #### 异步遍历的接口

异步遍历器的最大的语法特点，就是调用遍历器的next方法，返回的是一个 Promise 对象。


异步遍历器与同步遍历器最终行为是一致的，只是会先返回 Promise 对象，作为中介。

```js
async function* createAsyncIterable(syncIterable) {
  for (const elem of syncIterable) {
    yield elem;
  }
}
async function f() {
  const asyncIterable = createAsyncIterable(['a', 'b']);
  const asyncIterator = asyncIterable[Symbol.asyncIterator]();
  console.log(await asyncIterator.next());
  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
  // { value: 'b', done: false }
  console.log(await asyncIterator.next());
  // { value: undefined, done: true }
}
f()

Promise {<fulfilled>: undefined}
```

```js
async function* createAsyncIterable(syncIterable) {
  for (const elem of syncIterable) {
    yield elem;
  }
}

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

asyncIterator
.next()
.then(iterResult1 => {
  console.log(iterResult1); // { value: 'a', done: false }
  return asyncIterator.next();
})
.then(iterResult2 => {
  console.log(iterResult2); // { value: 'b', done: false }
  return asyncIterator.next();
})
.then(iterResult3 => {
  console.log(iterResult3); // { value: undefined, done: true }
});

```

#### for await...of
for...of循环用于遍历同步的 Iterator 接口

```js
async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
```

#### 异步 Generator 函数

##### 普通函数
--
##### async 函数

async函数是ES2017提供的异步函数语法，是generator的语法糖，但是用法上与Generator函数还是有很大不同；

特征：
在function关键字前面跟一个async关键字；
在函数体内部使用await表达式；
async函数返回一个promise对象；

用法：
执行async函数会立刻执行，和普通函数一样，但是返回一个promise对象；


##### Generator函数：
Generator函数是ES2015提供的异步解决方案，与普通函数有很大的不同；

特征：
在function关键字后面跟一个(*)号;
在函数体内部使用yield表达式作为一个状态；
Generator函数返回一个遍历器，可通过for……of方法遍历每个状态；

用法：
执行Generator并不立刻执行，返回一个遍历器，遍历器通过调用next()、throw()或者return()执行下一个状态、捕获错误或者结束遍历器；


生成器本质上是一种特殊的迭代器
生成器部署了迭代器接口，因此可以用for...of来遍历，不用调用next方法
```js
 function *foo() {
  yield 1
  yield 2
  yield 3
  return 4
}

for (let v of foo()) {
  console.log(v)
}
```
    
##### 异步 Generator 函数。

异步 Generator 函数就是async函数与 Generator 函数的结合。
```js
async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
```
异步 Generator 函数就是async函数与 Generator 函数的结合。
异步遍历器的设计目的之一，就是 Generator 函数处理同步操作和异步操作时，能够使用同一套接口。

```js
// 同步 Generator 函数
function* map(iterable, func) {
  const iter = iterable[Symbol.iterator]();
  while (true) {
    const {value, done} = iter.next();
    if (done) break;
    yield func(value);
  }
}

// 异步 Generator 函数
async function* map(iterable, func) {
  const iter = iterable[Symbol.asyncIterator]();
  while (true) {
    const {value, done} = await iter.next();
    if (done) break;
    yield func(value);
  }
}
```

#### 5. yield* 语句

```js
async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}
async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}
```


-------------------------------------------------------
ArrayBuffer



二进制数组由三类对象组成。
（1）ArrayBuffer对象：代表内存之中的一段二进制数据，可以通过“视图”进行操作。“视图”部署了数组接口，这意味着，可以用数组的方法操作内存。
（2）TypedArray视图：共包括 9 种类型的视图，比如Uint8Array（无符号 8 位整数）数组视图, Int16Array（16 位整数）数组视图, Float32Array（32 位浮点数）数组视图等等。
（3）DataView视图：可以自定义复合格式的视图，比如第一个字节是 Uint8（无符号 8 位整数）、第二、三个字节是 Int16（16 位整数）、第四个字节开始是 Float32（32 位浮点数）等等，此外还可以自定义字节序。

ArrayBuffer 对象

ArrayBuffer对象代表储存二进制数据的一段内存，它不能直接读写，只能通过视图（TypedArray视图和DataView视图)来读写，视图的作用是以指定格式解读二进制数据。
ArrayBuffer也是一个构造函数，可以分配一段可以存放数据的连续内存区域。

由于两个视图对应的是同一段内存，一个视图修改底层内存，会影响到另一个视图。
const buffer = new ArrayBuffer(12);

const x1 = new Int32Array(buffer);
x1[0] = 1;
console.log(x1);
const x2 = new Uint8Array(buffer);
x2[0]  = 2;
console.log(x1);
console.log(x2);
ArrayBuffer实例的byteLength属性，返回所分配的内存区域的字节长度。

ArrayBuffer.prototype.byteLength
ArrayBuffer实例的byteLength属性，返回所分配的内存区域的字节长度。
如果要分配的内存区域很大，有可能分配失败（因为没有那么多的连续空余内存），所以有必要检查是否分配成功。

ArrayBuffer.prototype.slice()
ArrayBuffer实例有一个slice方法，允许将内存区域的一部分，拷贝生成一个新的ArrayBuffer对象。

ArrayBuffer.isView()
ArrayBuffer有一个静态方法isView，返回一个布尔值，表示参数是否为ArrayBuffer的视图实例。这个方法大致相当于判断参数，是否为TypedArray实例或DataView实例。 


TypedArray 视图
概述
ArrayBuffer对象作为内存区域，可以存放多种类型的数据。同一段内存，不同数据有不同的解读方式，这就叫做“视图”（view）
TypedArray视图一共包括 9 种类型，每一种视图都是一种构造函数。
* Int8Array：8 位有符号整数，长度 1 个字节。
* Uint8Array：8 位无符号整数，长度 1 个字节。
* Uint8ClampedArray：8 位无符号整数，长度 1 个字节，溢出处理不同。
* Int16Array：16 位有符号整数，长度 2 个字节。
* Uint16Array：16 位无符号整数，长度 2 个字节。
* Int32Array：32 位有符号整数，长度 4 个字节。
* Uint32Array：32 位无符号整数，长度 4 个字节。
* Float32Array：32 位浮点数，长度 4 个字节。
* Float64Array：64 位浮点数，长度 8 个字节。
* TypedArray 数组的所有成员，都是同一种类型。
* TypedArray 数组的成员是连续的，不会有空位。
* TypedArray 数组成员的默认值为 0。比如，new Array(10)返回一个普通数组，里面没有任何成员，只是 10 个空位；new Uint8Array(10)返回一个 TypedArray 数组，里面 10 个成员都是 0。
* TypedArray 数组只是一层视图，本身不储存数据，它的数据都储存在底层的ArrayBuffer对象之中，要获取底层对象必须使用buffer属性。
（1）TypedArray(buffer, byteOffset=0, length?)
（2）TypedArray(length)
视图还可以不通过ArrayBuffer对象，直接分配内存而生成。
（3）TypedArray(typedArray)
（4）TypedArray(arrayLikeObject)
构造函数的参数也可以是一个普通数组，然后直接生成TypedArray实例。

字节序
字节序指的是数值在内存中的表示方式。
如果一段数据是大端字节序，TypedArray 数组将无法正确解析，因为它只能处理小端字节序！
所有个人电脑几乎都是小端字节序，所以 TypedArray 数组内部也采用小端字节序读写数据，或者更准确的说，按照本机操作系统设定的字节序读写数据。

溢出
根据规则，只会保留 当前的 view
负数在计算机内部采用“2 的补码”表示，也就是说，将对应的正数值进行否运算，然后加1。

TypedArray.prototype.buffer
const a = new Float32Array(64);
const b = new Uint8Array(a.buffer);
TypedArray.prototype.byteLength
    byteLength属性返回 TypedArray 数组占据的内存长度，单位为字节。
TypedArray.prototype.byteOffset
    byteOffset属性返回 TypedArray 数组从底层ArrayBuffer对象的哪个字节开始。
TypedArray.prototype.length
    length属性表示 TypedArray 数组含有多少个成员
TypedArray.prototype.set
    set方法用于复制数组（普通数组或 TypedArray 数组），也就是将一段内容完全复制到另一段内存。
TypedArray.prototype.subarray
    subarray方法是对于 TypedArray 数组的一部分，再建立一个新的视图。
TypedArray.prototype.slice
    slice方法，可以返回一个指定位置的新的TypedArray实例。
TypedArray.of() 
    都有一个静态方法of，用于将参数转为一个TypedArray实例。
TypedArray.from()
    接受一个可遍历的数据结构（比如数组）作为参数，返回一个基于这个结构的TypedArray实例。

3.复合视图
由于视图的构造函数可以指定起始位置和长度，所以在同一段内存之中，可以依次存放不同类型的数据，这叫做“复合视图”。
4.DataView 视图

DataView视图本身也是构造函数，接受一个ArrayBuffer对象作为参数，生成视图。
DataView实例有以下属性，含义与TypedArray实例的同名方法相同。
* DataView.prototype.buffer：返回对应的 ArrayBuffer 对象
* DataView.prototype.byteLength：返回占据的内存字节长度
* DataView.prototype.byteOffset：返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始
DataView实例提供 8 个方法读取内存。
* getInt8：读取 1 个字节，返回一个 8 位整数。
* getUint8：读取 1 个字节，返回一个无符号的 8 位整数。
* getInt16：读取 2 个字节，返回一个 16 位整数。
* getUint16：读取 2 个字节，返回一个无符号的 16 位整数。
* getInt32：读取 4 个字节，返回一个 32 位整数。
* getUint32：读取 4 个字节，返回一个无符号的 32 位整数。
* getFloat32：读取 4 个字节，返回一个 32 位浮点数。
* getFloat64：读取 8 个字节，返回一个 64 位浮点数。
DataView 视图提供 8 个方法写入内存。
* setInt8：写入 1 个字节的 8 位整数。
* setUint8：写入 1 个字节的 8 位无符号整数。
* setInt16：写入 2 个字节的 16 位整数。
* setUint16：写入 2 个字节的 16 位无符号整数。
* setInt32：写入 4 个字节的 32 位整数。
* setUint32：写入 4 个字节的 32 位无符号整数。
* setFloat32：写入 4 个字节的 32 位浮点数。
* setFloat64：写入 8 个字节的 64 位浮点数。
* 
* 
WebSocket

let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';

// Wait until socket is open
socket.addEventListener('open', function (event) {
  // Send binary data
  const typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});

// Receive binary data
socket.addEventListener('message', function (event) {
  const arrayBuffer = event.data;
  // ···
});
